/*****************************************************************************
 * plum_socket_lwip.c
 *
 * Copyright (C) 2020 Jeasonvor 1101627719@qq.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ****************************************************************************/

/*****************************************************************************
 * Included Files
 ****************************************************************************/
#include "plum_api.h"
#include "lwip/sockets.h"
/*****************************************************************************
 * Trace Definitions
 ****************************************************************************/

/*****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/*****************************************************************************
 * Private Types
 ****************************************************************************/

/*****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/*****************************************************************************
 * Private Data
 ****************************************************************************/

/*****************************************************************************
 * Public Data
 ****************************************************************************/

/*****************************************************************************
 * Private Functions
 ****************************************************************************/

PLUM_PRIVATE
plum_s32 posix_socket_family(plum_socket_addr_family_t family)
{
    switch (family) {
        case PLUM_ADDRESS_FAMILY_AF_UNSPEC:
            return AF_UNSPEC;
        case PLUM_ADDRESS_FAMILY_AF_INET:
            return AF_INET;
        case PLUM_ADDRESS_FAMILY_AF_INET6:
            return AF_INET6;
        default:
            return (AF_UNSPEC);
    }
}

PLUM_PRIVATE
plum_s32 posix_socket_type(plum_socket_type_t type)
{
    switch (type) {
        case PLUM_SOCKET_TYPE_TCP:
            return SOCK_STREAM;
        case PLUM_SOCKET_TYPE_UDP:
            return SOCK_DGRAM;
        case PLUM_SOCKET_TYPE_RAW:
            return SOCK_RAW;
    }
}

/**
 * @brief:  plum IP地址转网络socket地址
 *
 * @param[in]:  ipaddr  -   plum IP地址
 * @param[out]: addr    -   网络socket地址
 *
 * @return: 返回执行状态
 */
PLUM_PRIVATE
plum_s32 posix_socket_ipaddr2sockaddr(plum_ipaddr_t       ipaddr,
                                      struct sockaddr_in *addr)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (addr == plum_null) {
            LOG_E("the parameter is Invalid");
            rc = PLUM_ECODE_INVALID;
            break;
        }

        addr->sin_family = posix_socket_family(ipaddr.family);
        addr->sin_port   = htons(ipaddr.port);
        if (ipaddr.addr != plum_null) {
            addr->sin_addr.s_addr = inet_addr(ipaddr.addr);
        }
        else {
            addr->sin_addr.s_addr = htonl(INADDR_ANY);
        }
    } while (0);

    return (rc);
}

/**
 * @brief:  网络socket地址转plum IP地址
 *
 * @param[in]:  addr    -   网络socket地址
 * @param[out]: ipaddr  -   plum IP地址
 *
 * @return: 返回执行状态
 */
PLUM_PRIVATE
plum_s32 posix_socket_sockaddr2ipaddr(struct sockaddr_in addr,
                                      plum_ipaddr_t *    ipaddr)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (ipaddr == plum_null) {
            LOG_E("the parameter is Invalid");
            rc = PLUM_ECODE_INVALID;
            break;
        }

        snprintf(ipaddr->addr, sizeof(ipaddr->addr), "%s",
                 inet_ntoa(addr.sin_addr));
        ipaddr->port = ntohs(addr.sin_port);
    } while (0);

    return (rc);
}

/*****************************************************************************
 * Public Functions
 ****************************************************************************/

PLUM_PUBLIC
plum_s32 plum_cal_socket_create(plum_socket_addr_family_t family,
                                plum_socket_type_t        type)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        rc = socket(posix_socket_family(family), posix_socket_type(type), 0);
        if (rc < 0) {
            LOG_E("socekt err,rc: %d", errno);
            rc = PLUM_ECODE_EIO;
            break;
        }

        // socket复用
        int flag = 1;
        setsockopt(rc, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_socket_bind(plum_u32 socketfd, plum_ipaddr_t addr)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        struct sockaddr_in local_addr = {0};
#if 0
        memset(&sockaddr, 0, sizeof(sockaddr));
        sockaddr.sin_family = posix_socket_family(addr.family);
        sockaddr.sin_port   = htons(addr.port);
        if (addr.addr) {
            // sockaddr.sin_addr.s_addr = inet_addr(addr);
            inet_pton(sockaddr.sin_family, addr.addr, &sockaddr.sin_addr.s_addr);
        }
        else {
            sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        }
#else
        rc = posix_socket_ipaddr2sockaddr(addr, &local_addr);
        if (rc) {
            LOG_E("posix_socket_ipaddr2sockaddr err,rc : %d", rc);
            break;
        }
#endif
        //结构体struct sockaddr和struct sockaddr_in长度一致
        rc = bind(socketfd, (struct sockaddr *)&local_addr,
                  sizeof(struct sockaddr));
        if (rc) {
            LOG_E("bind err,rc: %d", errno);
            rc = PLUM_ECODE_EIO;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_socket_connect(plum_u32 socekfd, plum_ipaddr_t addr)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        struct sockaddr_in dst_addr = {0};
#if 0
        dst_addr.sin_port           = htons(addr.port);
        dst_addr.sin_family         = posix_socket_family(addr.family);
        dst_addr.sin_addr.s_addr    = inet_addr(addr.addr);
#else
        rc = posix_socket_ipaddr2sockaddr(addr, &dst_addr);
        if (rc) {
            LOG_E("posix_socket_ipaddr2sockaddr err, rc: %d", rc);
            break;
        }
#endif
        rc = connect(socekfd, (struct sockaddr *)&dst_addr,
                     sizeof(struct sockaddr));
        if (rc) {
            LOG_E("connect error,rc: %d", errno);
            perror("reason");
            rc = PLUM_ECODE_EIO;
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_socket_listen(plum_u32 socketfd, plum_u32 max)
{
    plum_s32 rc = PLUM_ECODE_OK;

    rc = listen(socketfd, max);
    if (rc) {
        LOG_E("listen err,rc: %d", errno);
        rc = PLUM_ECODE_EIO;
    }

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_socket_accept(plum_u32 socektfd, plum_ipaddr_t *addr)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        struct sockaddr_in peer;
        socklen_t          peer_len = sizeof(struct sockaddr_in);
        rc = accept(socektfd, (struct sockaddr *)&peer, &peer_len);
        if (rc < 0) {
            LOG_E("accept err,rc: %d", errno);
            rc = PLUM_ECODE_EIO;
            break;
        }
        else if (addr != plum_null) {
            rc = posix_socket_sockaddr2ipaddr(peer, addr);
            if (rc) {
                LOG_E("posix_socket_sockaddr2ipaddr err,rc : %d", rc);
                break;
            }
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_socket_recv(plum_u32 socektfd, plum_void *data, plum_u32 size,
                              plum_u32 timeout)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!socektfd) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        if (!data) {
            rc = PLUM_ECODE_INVALID;
            break;
        }

        struct timeval value;
        value.tv_sec  = timeout / 1000;
        value.tv_usec = 0;  // timeout *
        setsockopt(socektfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&value,
                   sizeof(value));

        rc = recv(socektfd, data, size, 0);
        if (rc < 0) {
            LOG_E("socket recv err,rc : %d", errno);
            rc = PLUM_ECODE_EIO;
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_cal_socekt_send(plum_u32 socektfd, plum_void *data, plum_u32 size)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!socektfd) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        if (!data) {
            rc = PLUM_ECODE_INVALID;
            break;
        }

        rc = send(socektfd, data, size, 0);
        if (rc < 0) {
            LOG_E("socket send err,rc : %d", errno);
            perror("reason");
            rc = PLUM_ECODE_EIO;
            break;
        }

    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_void plum_cal_socekt_close(plum_u32 socktfd)
{
    close(socktfd);
}

/****************************************************************************/
/*                                                                          */
/*  End of file.                                                            */
/*                                                                          */
/****************************************************************************/
